package org.ymf.service.impl;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

import cn.hutool.core.io.FileUtil;

import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.layuiframework.stereotype.exception.APIException;
import org.layuiframework.stereotype.util.JwtUtil;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import javax.annotation.Resource;

import org.ymf.constant.ChartStatus;
import org.ymf.constant.ChartType;
import org.ymf.dao.ChartEsDao;
import org.ymf.entity.ChartEs;
import com.alibaba.excel.EasyExcel;
import com.github.pagehelper.PageInfo;
import com.github.pagehelper.PageHelper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.layuiframework.stereotype.exception.ThrowUtils;
import org.layuiframework.stereotype.vo.ResultCode;
import org.ymf.entity.Chart;
import org.ymf.mapper.ChartMapper;
import org.ymf.service.ChartService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.ymf.util.ExcelUtils;
import org.ymf.vo.ChartVo;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import org.layuiframework.stereotype.annotation.ImportFromFile;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * <p>
 * 图表信息表 服务实现类
 * </p>
 *
 * @author Malphite
 * @since 2023-08-01
 */
@Service
public class ChartServiceImpl extends ServiceImpl<ChartMapper, Chart> implements ChartService {

    @Resource
    ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Resource
    ChartEsDao chartEsDao;

    @Resource
    AiManager aiManager;

    @Resource
    ThreadPoolExecutor threadPoolExecutor;

    @Override
    public PageInfo<Chart> pageChart(ChartVo vo){
        PageHelper.startPage(vo.getPage(), vo.getLimit());
        QueryWrapper<Chart> queryWrapper = new QueryWrapper<>();
        queryWrapper.like(StringUtils.hasLength(vo.getName()), "name" , vo.getName());
        if(null != vo.getEndTime()) vo.setEndTime(new Date(vo.getEndTime().getTime() + 24 * 60 * 60 * 1000));
        queryWrapper.ge(null != vo.getStartTime(), "updateTime", vo.getStartTime());
        queryWrapper.le(null != vo.getEndTime(), "updateTime", vo.getEndTime());
        queryWrapper.orderByDesc(" updateTime ");
        List<Chart> dtos = this.list(queryWrapper);
        return new PageInfo(dtos);
    }

    @Override
    public List<Chart> listChart(ChartVo vo){
        if(null != vo.getEndTime()) vo.setEndTime(new Date(vo.getEndTime().getTime() + 24 * 60 * 60 * 1000));
        return this.list(new QueryWrapper<Chart>()
            .like(StringUtils.hasLength(vo.getName()), "name" , vo.getName())
            .ge(null != vo.getStartTime(), "updateTime", vo.getStartTime())
            .le(null != vo.getEndTime(), "updateTime", vo.getEndTime())
            .orderByDesc(" updateTime ")
        );
    }

    @ImportFromFile
    @Override
    public List<Chart> excelChart(MultipartFile file, List<Chart> objects, HttpServletRequest request){
        for (Chart chart: objects) {
            // TODO 在这里修改实体类的信息
            chart.setId(null);
        }
        return objects;
    }

    @Override
    public boolean addChart(MultipartFile multipartFile, Chart chart, HttpServletRequest request){
        // 文件校验
        long size = multipartFile.getSize();
        String originalFilename = multipartFile.getOriginalFilename();
        // 校验文件大小
        final long ONE_MB = 1024 * 1024L;
        ThrowUtils.throwIf(size > ONE_MB,ResultCode.NETERROR, "文件超过 1M");
        // 校验文件后缀 aaa.png
        String suffix = FileUtil.getSuffix(originalFilename);
        final List<String> validFileSuffixList = Arrays.asList("xlsx", "xls");
        ThrowUtils.throwIf(!validFileSuffixList.contains(suffix), ResultCode.NETERROR, "文件后缀非法");
        // TODO 追加信息，比如从请求对象中将用户信息取出来放入对象中
        // chart.setCreateuser("");
        String token = request.getHeader("Authorization");
        /**
         * 这里的用户信息是以username的字符串保存的。
         */
        String username = JwtUtil.getUsername(token);
        chart.setUserid(1L);
        // 默认都是以等待开始的
        chart.setStatus(ChartStatus.WAIT.getStatus());
        // 保存信息,如果失败抛出异常去处理
        ThrowUtils.throwIf(!this.save(chart), ResultCode.NETERROR);

        /**
         * 封装查询语句
         */
        // 限流判断，每个用户一个限流器
//        redisLimiterManager.doRateLimit("genChartByAi_" + username);
        long biModelId = 1659171950288818178L;
        // 构造用户输入
        StringBuilder userInput = new StringBuilder();
        userInput.append("分析需求：").append("\n");

        // 拼接分析目标
        String userGoal = chart.getGoal();
        userGoal += "，请使用" + ChartType.getNameByType(chart.getCharttype());
        userInput.append(userGoal).append("\n");
        userInput.append("原始数据：").append("\n");
        // 压缩后的数据
        String csvData = ExcelUtils.excelToCsv(multipartFile);
        userInput.append(csvData).append("\n");

        String result = aiManager.doChat(biModelId, userInput.toString());
        String[] splits = result.split("【【【【【");
        if (splits.length < 3) {
            throw new APIException(ResultCode.NETERROR, "AI 生成错误");
        }
        String genChart = splits[1].trim();
        String genResult = splits[2].trim();
        chart.setGenchart(genChart);
        chart.setGenresult(genResult);
        chart.setChartdata(csvData);
        chart.setStatus(ChartStatus.SUCCEED.getStatus());
        ThrowUtils.throwIf(!this.updateById(chart), ResultCode.NETERROR);
        return true;
    }

    @Override
    public boolean addChartSync(MultipartFile multipartFile, Chart chart, HttpServletRequest request){
        // 文件校验
        long size = multipartFile.getSize();
        String originalFilename = multipartFile.getOriginalFilename();
        // 校验文件大小
        final long ONE_MB = 1024 * 1024L;
        ThrowUtils.throwIf(size > ONE_MB,ResultCode.NETERROR, "文件超过 1M");
        // 校验文件后缀 aaa.png
        String suffix = FileUtil.getSuffix(originalFilename);
        final List<String> validFileSuffixList = Arrays.asList("xlsx", "xls");
        ThrowUtils.throwIf(!validFileSuffixList.contains(suffix), ResultCode.NETERROR, "文件后缀非法");
        // TODO 追加信息，比如从请求对象中将用户信息取出来放入对象中
        // chart.setCreateuser("");
        String token = request.getHeader("Authorization");
        /**
         * 这里的用户信息是以username的字符串保存的。
         */
        String username = JwtUtil.getUsername(token);
        chart.setUserid(1L);
        // 默认都是以等待开始的
        chart.setStatus(ChartStatus.WAIT.getStatus());
        // 保存信息,如果失败抛出异常去处理
        ThrowUtils.throwIf(!this.save(chart), ResultCode.NETERROR);

        long biModelId = 1659171950288818178L;
        // 构造用户输入
        StringBuilder userInput = new StringBuilder();
        userInput.append("分析需求：").append("\n");

        // 拼接分析目标
        String userGoal = chart.getGoal();
        userGoal += "，请使用" + ChartType.getNameByType(chart.getCharttype());
        userInput.append(userGoal).append("\n");
        userInput.append("原始数据：").append("\n");
        // 压缩后的数据
        String csvData = ExcelUtils.excelToCsv(multipartFile);
        userInput.append(csvData).append("\n");

        CompletableFuture.runAsync(() -> {
            // 先修改图表任务状态为 “执行中”。等执行成功后，修改为 “已完成”、保存执行结果；执行失败后，状态修改为 “失败”，记录任务失败信息。
            chart.setChartdata(csvData);
            chart.setStatus(ChartStatus.RUNNING.getStatus());
            if(!this.updateById(chart)){
                handleChartUpdateError(chart, "更新图表执行中状态失败");
                return;
            }

            // 调用 AI
            String result = aiManager.doChat(biModelId, userInput.toString());
            String[] splits = result.split("【【【【【");
            if (splits.length < 3) {
                handleChartUpdateError(chart, "AI 生成错误");
                return;
            }
            String genChart = splits[1].trim();
            String genResult = splits[2].trim();

            chart.setGenchart(genChart);
            chart.setGenresult(genResult);
            chart.setStatus(ChartStatus.SUCCEED.getStatus());
            if(!this.updateById(chart)){
                handleChartUpdateError(chart, "更新图表成功状态失败");
                return;
            }
        }, threadPoolExecutor);
        return true;
    }

    private void handleChartUpdateError(Chart chart, String execMessage) {
        chart.setStatus(ChartStatus.FAILED.getStatus());
        chart.setExecmessage(execMessage);
        boolean updateResult = this.updateById(chart);
        if (!updateResult) {
            log.error("更新图表失败状态失败" + chart.getId() + "," + execMessage);
        }
    }


    @Override
    public boolean updateChart(Chart chart){
        // 保存信息,如果失败抛出异常去处理
        ThrowUtils.throwIf(!this.updateById(chart), ResultCode.NETERROR);
        return true;
    }

    @Override
    public boolean deleteChart(Chart chart){
        // 保存信息,如果失败抛出异常去处理
        ThrowUtils.throwIf(!this.removeById(chart), ResultCode.NETERROR);
        return true;
    }

    @Override
    public void exportChart(ChartVo vo, HttpServletResponse response) throws IOException {
        response.setHeader("content-Type", "application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        // TODO 修改excel文件标题
        String title = "测试";
        // TODO 修改excel文件名称
        String filename = java.net.URLEncoder.encode(title, "UTF-8") + ".xlsx";
        // TODO 修改excel文件sheet名称
        String sheetname = "sheet1";
        response.setHeader("Content-Disposition", "attachment;filename=" + filename);
        QueryWrapper<Chart> queryWrapper = new QueryWrapper<>();
        queryWrapper.like(StringUtils.hasLength(vo.getName()), "name" , vo.getName());
        if(null != vo.getEndTime()) vo.setEndTime(new Date(vo.getEndTime().getTime() + 24 * 60 * 60 * 1000));
        queryWrapper.ge(null != vo.getStartTime(), "updateTime", vo.getStartTime());
        queryWrapper.le(null != vo.getEndTime(), "updateTime", vo.getEndTime());
        queryWrapper.orderByDesc(" updateTime ");
        List<Chart> exports = this.list(queryWrapper);
        ServletOutputStream outputStream = response.getOutputStream();
        EasyExcel.write(outputStream, Chart.class).head(Chart.class)
            .sheet(sheetname)
            .useDefaultStyle(true).relativeHeadRowIndex(2).doWrite(exports);
        outputStream.flush();
    }

    @Override
    public PageInfo<Chart> pageChartEs(ChartVo vo){
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        SortBuilder sortBuilder = SortBuilders.scoreSort();
        if(StringUtils.hasLength(vo.getName()))
            boolQueryBuilder.must(QueryBuilders.multiMatchQuery(vo.getName(),"goal","name","chartData","genChart","genResult","execMessage"));
        if(null != vo.getEndTime()){
            vo.setEndTime(new Date(vo.getEndTime().getTime() + 24 * 60 * 60 * 1000));
            boolQueryBuilder.must(QueryBuilders.rangeQuery("updateTime").to(vo.getEndTime()));
        }
        if(null != vo.getStartTime())
            boolQueryBuilder.must(QueryBuilders.rangeQuery("updateTime").from(vo.getStartTime()));
        // sortBuilder = SortBuilders.fieldSort("updateTime");
        // sortBuilder.order(SortOrder.DESC);
        PageRequest pageRequest = PageRequest.of(vo.getPage(), vo.getLimit());
        List<HighlightBuilder.Field> fields = new ArrayList<>();
        fields.add(new HighlightBuilder.Field("goal"));
        fields.add(new HighlightBuilder.Field("name"));
        fields.add(new HighlightBuilder.Field("chartData"));
        fields.add(new HighlightBuilder.Field("genChart"));
        fields.add(new HighlightBuilder.Field("genResult"));
        fields.add(new HighlightBuilder.Field("execMessage"));
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder).withPageable(pageRequest).withSorts(sortBuilder).withHighlightFields(fields).build();
        SearchHits<ChartEs> searchHits = elasticsearchRestTemplate.search(searchQuery, ChartEs.class);
        PageInfo<Chart> page = new PageInfo<>();
        page.setTotal(searchHits.getTotalHits());
        List<Chart> resourceList = new ArrayList<>();
        if (searchHits.hasSearchHits()) {
            List<SearchHit<ChartEs>> searchHitList = searchHits.getSearchHits();
            Map<Long, List<SearchHit<ChartEs>>> searchHitMap = searchHitList.stream().collect(Collectors.groupingBy(searchHit -> searchHit.getContent().getId()));
            List<Long> chartIdList = searchHitList.stream().map(searchHit -> searchHit.getContent().getId()).collect(Collectors.toList());
            // 从数据库中取出更完整的数据
            List<Chart> chartList = baseMapper.selectBatchIds(chartIdList);
            if (chartList != null) {
                Map<Long, List<Chart>> idChartMap = chartList.stream().collect(Collectors.groupingBy(Chart :: getId));
                chartIdList.forEach(chartId -> {
                    if (idChartMap.containsKey(chartId)) {
                        Chart chart = idChartMap.get(chartId).get(0);
                        String highlightContent = searchHitMap.get(chartId).get(0).getHighlightFields().values().stream().map(m -> m.get(0)).collect(Collectors.joining("|"));
                        chart.setElasticsearchHintcontent(highlightContent);
                        chart.setElasticsearchScore((double) searchHitMap.get(chartId).get(0).getScore());
                        resourceList.add(chart);
                    } else {
                        // 从 es 清空 db 已物理删除的数据
                        String delete = elasticsearchRestTemplate.delete(String.valueOf(chartId), ChartEs.class);
                    }
                });
            }
        }
        page.setList(resourceList);
        return page;
    }

}
